///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2007-2009 ILOG, S.A.
//  All Rights Reserved.
//  The following is ILOG Source Code.  No modifications may be made to the  
//  ILOG Source Code.  Any usage of the ILOG Source Code is subject to 
//  the terms and conditions of the ILOG End User License Agreement   
//  applicable to this ILOG software product.
//
///////////////////////////////////////////////////////////////////////////////

package ilog.gauges.rectangular.renderers
{
  import flash.display.Graphics;
  import flash.geom.Rectangle;
  
  import ilog.gauges.gaugesClasses.rectangular.RectangularValueRenderer;
  import ilog.utils.CSSUtil;
  
  import mx.graphics.IFill;
  import mx.graphics.IStroke;
  import mx.graphics.SolidColor;
  import mx.styles.CSSStyleDeclaration;

  /**
   * Specifies the stroke for the marker.
   * This style must be an instance of an implementation of the <code>IStroke</code> interface.  
   * 
   * @default undefined
   */
  [Style(name="stroke", type="mx.graphics.IStroke", inherit="no")]

  /**
   * Specifies the fill for the marker.
   * This style must be an instance of an implementation of the <code>IFill</code> interface.
   * 
   * @default 0xFF0000
   */
  [Style(name="fill", type="mx.graphics.IFill", inherit="no")]
  
  /**
   * Specifies the form of the marker shape. It can be one of:
   * <ul>
   *   <li><code>rectangle</code>: a rectangle is displayed at the marker position</li>
   *   <li><code>arrow</code>: an arrow is displayed at the marker position unless placement is <code>middle</code>, 
   *   in which case a double-arrow (pointing both ways) is displayed.</li>
   *   <li><code>ellipse</code>: an ellipse is displayed at the marker position; if <code>length</code> and <code>thickness</code> are equal a circle is drawn.</li>
   * </ul>
   * 
   * @default rectangle
   * @ilog.reviewed SH 17 June 08
   */  
  [Style(name="form", enumeration="rectangle,arrow, ellipse", type="String", inherit="no")]

  /**
   * A default marker renderer for rectangular gauges. A marker is a visual
   * element that represents a value on a scale using a shape drawn at the 
   * position corresponding to the value. The <code>form</code> style property 
   * allows you to change the shape used to draw the marker.
   * 
   * @mxml
   *  
   * <p>The <code>&lt;ilog:MarkerRenderer&gt;</code> tag inherits all the properties
   * of its parent classes and adds the following properties:</p>
   *  
   * <pre>
   * &lt;ilog:MarkerRenderer
   *   <strong>Properties</strong>
   *   thickness="<i>0.5%</i>"
   *   length="<i>100%</i>"
   *   placement="middle|leading|trailling"
   * 
   *   <strong>Styles</strong>
   *   fill="<i>0xFF0000</i>"
   *   stroke="<i>No default</i>" 
   *   form="rectangle|arrow|ellipse"
   * /&gt;
   * </pre>
   * 
   * @includeExample examples/MarkerRendererExample.mxml -noswf
   */
  public class MarkerRenderer extends RectangularValueRenderer
  {    
    private static var stylesInited:Boolean = initStyles(); 

    private static function initStyles():Boolean
    {
      var nStyle:CSSStyleDeclaration =
        CSSUtil.createSelector("MarkerRenderer");   
            
      nStyle.defaultFactory = function():void  {
        this.fill = new SolidColor(0xff0000);
        this.form = "rectangle";
      }

      return true;
    }    
   
    /**
     * Constructor.
     */
    public function MarkerRenderer() 
    {
      
    }
    
    private var _percentThickness:Number = 0.5;

    [Inspectable(environment="none")]

    /**
     * Number that specifies the thickness of the marker as a percentage
     * of either the width of the marker (in a horizontal gauge) or
     * the height of the marker (in a vertical gauge). Allowed 
     * values are 0-100.  Setting the <code>thickness</code> property resets 
     * this property to <code>NaN</code>.
     * 
     * @default 0.5
     * @see #thickness
     */    
    public function get percentThickness():Number
    {
      return _percentThickness;
    }   

    /**
     * @private
     */
    public function set percentThickness(value:Number):void
    {
      _percentThickness = value;
      if (!isNaN(_percentThickness))
        _thickness = NaN;
      invalidateDisplayList();
    }

    private var _thickness:Number = NaN;

    [Inspectable(category="Gauge")]
    [PercentProxy("percentThickness")]

    /**
     * Number that specifies the thickness in pixels of the marker
     * in the marker coordinates.
     *  
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>thickness</code> attribute, such as <code>thickness="1%"</code>,
     * but you cannot use a percentage value in the <code>thickness</code>
     * property in ActionScript.
     * Use the <code>percentThickness</code> property instead.</p>
     * 
     * @default NaN
     * @see #percentThickness
     */    
    public function get thickness():Number
    {
      return _thickness;
    }

    /**
     * @private
     */
    public function set thickness(value:Number):void
    {
      _thickness = value;
      if (!isNaN(_thickness))
        _percentThickness = NaN;
      invalidateDisplayList();      
    }
    
    private function getThickness():Number 
    {
      var ret:Number;
      if (isNaN(thickness)) {
        if (gauge.direction == "horizontal") {
          ret = unscaledWidth * percentThickness / 100;
        } else {
          ret = unscaledHeight * percentThickness / 100;   
        }
      } else {
        ret = thickness;
      }
      return ret;
    }
       
    private var _percentLength:Number = 100;

    [Inspectable(environment="none")]

    /**
     * Number that specifies the length of the marker as a percentage
     * of either the height of the marker (in a horizontal gauge) 
     * or the width of the marker (in a vertical gauge). Allowed 
     * values are 0-100. Setting the <code>length</code> property resets 
     * this property to <code>NaN</code>.
     * 
     * @default 100
     * @see #length
     */  
    public function get percentLength():Number
    {
      return _percentLength;
    }   
  
    /**
     * @private
     */ 
    public function set percentLength(value:Number):void
    {
      _percentLength = value;
      if (!isNaN(_percentLength))
        _length = NaN;
      invalidateDisplayList()        
    }

    private var _length:Number = NaN;

    [Inspectable(category="Gauge")]
    [PercentProxy("percentLength")]

    /**
     * Number that specifies the length, in pixels, of the marker
     * in the marker coordinates.
     *  
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>length</code> attribute, such as <code>length="50%"</code>,
     * but you cannot use a percentage value in the <code>length</code>
     * property in ActionScript.
     * Use the <code>percentLength</code> property instead.</p>
     * 
     * @default NaN
     * @see #percentLength
     */  
    public function get length():Number
    {
      return _length;
    }

    /**
     * @private
     */
    public function set length(value:Number):void
    {
      _length = value;
      if (!isNaN(_length))
        _percentLength = NaN;
      invalidateDisplayList();      
    }
    
    private function getLength():Number 
    {
      var ret:Number;
      if (isNaN(length)) {
        if (gauge.direction == "horizontal") {
          ret = unscaledHeight * percentLength / 100;
        } else {
          ret = unscaledWidth * percentLength / 100;   
        }
      } else {
        ret = length;
      }
      return ret;
    }  
    
    private var _placement:String = "middle"; 
    
    [Inspectable(category="Gauge", defaultValue="middle", enumeration="leading,middle,trailing", type="String")]
   
    /**
     * The placement of the marker. Possible values are:
     * <ul>
     *   <li><code>"middle"</code> (default): The marker is centered vertically when the gauge is horizontal and horizontally when the gauge is vertical.</li>
     *   <li><code>"leading"</code>: The marker is placed at the top of the marker bounds when the gauge is horizontal and on the left when the gauge is vertical.</li>
     *   <li><code>"trailing"</code>: The marker is placed at the bottom of the marker bounds when the gauge is horizontal and on the right when the gauge is vertical.</li>
     * </ul>
     * 
     * @default "middle"
     */
    public function get placement():String
    {
      return _placement;
    }
    
    /**
     * @private
     */
    public function set placement(value:String):void
    {
      _placement = value;
      invalidateDisplayList();
    }    
    
    /**
     * Draws the marker. Subclasses can redefine this method to draw a different
     * marker.
     * 
     * @param graphics The <code>Graphic</code> to draw in.
     * @param horizontal Indicates whether the marker must be horizontal or vertical.
     * @param x The <code>x</code> position of the marker bounds.
     * @param y The <code>y</code> position of the marker bounds.
     * @param width The width of the marker bounds.
     * @param height The height of the marker bounds.
     * @param fill The <code>IFill</code> to be used to fill the marker.
     * @param stroke The <code>IStroke</code> to be used to draw the marker.
     */
    protected function drawMarker(graphics:Graphics, horizontal:Boolean,
                                  x:Number, y:Number, width:Number, height:Number, 
                                  fill:IFill, stroke:IStroke):void         
    {
      if (stroke != null) {
        stroke.apply(graphics);
      } 
      if (fill != null) {
        fill.begin(graphics, new Rectangle(x, y, width, height));
      }
      var style:Object = getStyle("form");
      if (style == "rectangle") {
        graphics.drawRect(x, y, width, height);
      } else if (style == "ellipse") {
        var radius:Number = Math.min(width, height) / 2;
        graphics.drawEllipse(x, y, width, height);
      } else {
        if (horizontal)
          drawHArrow(graphics, x, y, width, height);
        else
          drawVArrow(graphics, x, y, width, height);
      }
      if (fill != null) {
        fill.end(graphics);
      }    
    } 

    private function drawVArrow(graphics:Graphics, 
                                x:Number, y:Number, width:Number, height:Number):void         
    {
      switch (placement) {
        case "leading":
          graphics.moveTo(x, y + height);
          graphics.lineTo(x + width / 2, y);
          graphics.lineTo(x + width, y + height);
          graphics.lineTo(x, y + height);
        break;
        case "middle":
          graphics.moveTo(x, y + height / 2);
          graphics.lineTo(x + width / 2, y);
          graphics.lineTo(x + width, y + height / 2);
          graphics.lineTo(x + width / 2, y + height);
          graphics.lineTo(x, y + height / 2);        
        break;
        case "trailing":
          graphics.moveTo(x, y);
          graphics.lineTo(x + width / 2, y + height);
          graphics.lineTo(x + width, y);
          graphics.lineTo(x, y);        
        break;
      }
    } 

    private function drawHArrow(graphics:Graphics, 
                                x:Number, y:Number, width:Number, height:Number):void         
    {
      // triangle
      switch (placement) {
        case "leading":
          graphics.moveTo(x + width, y);
          graphics.lineTo(x, y + height / 2);
          graphics.lineTo(x + width, y + height);
          graphics.lineTo(x + width, y);
        break;
        case "middle":
          graphics.moveTo(x + width / 2, y);
          graphics.lineTo(x, y + height / 2);
          graphics.lineTo(x + width / 2, y + height);
          graphics.lineTo(x + width, y + height / 2);
          graphics.lineTo(x + width / 2, y);        
        break;
        case "trailing":
          graphics.moveTo(x, y);
          graphics.lineTo(x + width, y + height / 2);
          graphics.lineTo(x, y + height);
          graphics.lineTo(x, y);        
        break;
      }
    }
    
    /**
     * Draws the marker by calling <code>drawMarker</code>.     
     *
     * @param unscaledWidth Specifies the width in pixels of the component
     * in the component's coordinates, regardless of the value of the
     * <code>scaleX</code> property of the component.
     *
     * @param unscaledHeight Specifies the height in pixels of the component
     * in the component's coordinates, regardless of the value of the
     * <code>scaleY</code> property of the component.
     * 
     * @see #drawMarker()
     */
    protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {     
      // call super in case we are the master to update the scale     
      super.updateDisplayList(unscaledWidth, unscaledHeight);
      
      var pos:Number = isNaN(transitionPosition) ? scale.positionForValue(value) : 
                                                   transitionPosition;
                                                   
      var delta:Number = 0;
                                            
      if (gauge.direction == "horizontal") {     
        switch (placement) {
          case "middle":
            delta = (unscaledHeight - getLength()) / 2;
            break;
          case "trailing":
            delta = unscaledHeight - getLength(); 
        }  
        drawMarker(graphics, false, pos - getThickness() / 2, delta, 
                   getThickness(), getLength(), getStyle("fill"), getStyle("stroke"));
      } else {
        // vertical
        switch (placement) {
          case "middle":
            delta = (unscaledWidth - getLength()) / 2;
            break;
          case "trailing":
            delta = unscaledWidth - getLength(); 
        }          
        drawMarker(graphics, true, delta, unscaledHeight - (pos + getThickness() / 2), 
                   getLength(), getThickness(), getStyle("fill"), getStyle("stroke"));        
      }      
    }
  }
}
